#include "StdAfx.h"
#include "ClassTree.h"
#include <wx/imaglist.h>
#include <wx/wupdlock.h>
#include "TagStorage.h"
#include "utils/macros.h"
#include <algorithm>
#include <malloc.h>
#include <windows.h>

BEGIN_EVENT_TABLE(ClassTree, wxTreeCtrl)
	EVT_KEY_DOWN(ClassTree::onKeyDown)
	EVT_TREE_SEL_CHANGED(wxID_ANY, ClassTree::onSelectionChanged)
END_EVENT_TABLE()

struct TagItemData : wxTreeItemData{
	TagItemData(const Tag* tag)
	: tagPtr(tag)
	{        
	}
	const Tag* tagPtr;;
};

#include "res/unknown.xpm"
#include "res/class.xpm"
#include "res/enum.xpm"
#include "res/method_impl.xpm"
#include "res/method_decl.xpm"
#include "res/member.xpm"
#include "res/typedef.xpm"
#include "res/define.xpm"

ClassTree::ClassTree(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
: imageList_(new wxImageList(16, 16))
, selectedTag_(0)
{
	imageList_->Add(wxBitmap((const char* const*)class_xpm));
	imageList_->Add(wxBitmap((const char* const*)enum_xpm));
	imageList_->Add(wxBitmap((const char* const*)method_impl_xpm));
	imageList_->Add(wxBitmap((const char* const*)method_decl_xpm));
	imageList_->Add(wxBitmap((const char* const*)typedef_xpm));
	imageList_->Add(wxBitmap((const char* const*)member_xpm));
	imageList_->Add(wxBitmap((const char* const*)enum_xpm)); // enum_value
	imageList_->Add(wxBitmap((const char* const*)define_xpm));
	imageList_->Add(wxBitmap((const char* const*)unknown_xpm));
	Create(parent, id, pos, size);
	AssignImageList(imageList_);
}


bool ClassTree::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
{
	if(!wxTreeCtrl::Create(parent, id, pos, size, wxTR_HAS_BUTTONS | wxTR_HIDE_ROOT | wxTR_LINES_AT_ROOT ))
		return false;

	wxTreeItemId root;
    root = AddRoot(wxT("Root"));

	return true;
}

template<bool methodsFirst, bool groupByAccess>
struct MembersCompare
{
	bool operator()(const Tag* lhs, const Tag* rhs) const{
		if(methodsFirst){
			if(lhs->type == TAG_FUNCTION){
				if(rhs->type == TAG_FUNCTION){
					return compareSymbolName(lhs->name, rhs->name);
				}
				else
					return true;
			}
			else{
				if(rhs->type == TAG_METHOD)
					return false;
				else
					return compareSymbolName(lhs->name, rhs->name);
			}
		}
	}
};

int tagIcon(const Tag& tag)
{
	return int(tag.type);
}


void ClassTree::insertTagItemMember(const Tag* tag)
{
	ClassNameToItemMap::iterator it = classNameToItem_.find(tag->parentClass);
	ClassInfo* info = 0;
	if(it != classNameToItem_.end()){
		info = &it->second;
		if(!info->items.empty()){
			if(matchFilterMember(*tag)){
				if(tag->type == TAG_FUNCTION)
					info->implementedMethods.push_back(tag->title);
				else if(tag->type == TAG_PROTOTYPE){
					if(std::find(info->implementedMethods.begin(), info->implementedMethods.end(), tag->title) != info->implementedMethods.end())
						return;
				}
			}
		}
		else
			info = 0;
	}
	if(info && matchFilterMember(*tag)){
		ClassInfo::Items::iterator it;
		FOR_EACH(info->items, it){
			wxTreeItemId item = AppendItem(*it, tag->title, tagIcon(*tag), -1, new TagItemData(tag));
			itemByTags_[tag] = item;
			if(tag->type == TAG_CLASS || tag->type == TAG_ENUM)
				classNameToItem_[tag->nameFull].items.insert(item);
			info->hasChildren = true;
		}
	}
}

void ClassTree::insertTagItem(const Tag* newTag)
{
	if(!matchFilter(*newTag)){
		//if(newTag->type == TAG_CLASS || newTag->type == TAG_ENUM)
		//	classNameToItem_[newTag->nameFull].items.clear();
		return;
	}

	wxTreeItemId root = GetRootItem();
	wxTreeItemIdValue cookie;
	wxTreeItemId item = GetFirstChild(root, cookie);
	TagCompare comparer;
	int index = 0;
	wxTreeItemId insertedItem;
	while(item.IsOk()){
		const Tag* tag = tagByItem(item);
		wxTreeItemId ii = itemByTag(tag);
		ASSERT(ii.IsOk());
		ASSERT(ii == item);
		if(comparer(newTag, tag)){
			insertedItem = InsertItem(root, size_t(index), newTag->title, tagIcon(*newTag), -1, new TagItemData(newTag));
			break;
		}
		item = GetNextChild(root, cookie);
		++index;
	}
	if(!insertedItem.IsOk())
		insertedItem = AppendItem(root, newTag->title, tagIcon(*newTag), -1, new TagItemData(newTag));
	
	if(newTag->type == TAG_CLASS || newTag->type == TAG_ENUM)
		classNameToItem_[newTag->nameFull].items.insert(insertedItem);
	itemByTags_[newTag] = insertedItem;
}


void ClassTree::update()
{
	wxWindowUpdateLocker locker(this);

	const TagStorage::DeadTags& deadTags = tagStorage.deadTags();
	TagStorage::DeadTags::const_iterator it;
	FOR_EACH(deadTags, it){
		const Tag* deadTag = *it;
		wxTreeItemId item = itemByTag(deadTag, true);
		if(item.IsOk())
			Delete(item);
		else{
			// FIXME: OutputDebugString("Failed to remove deadTag\n");
		}
	}
	const TagStorage::NewTags& newTags = tagStorage.newTags();
	FOR_EACH(newTags, it){
		const Tag* newTag = *it;

		if(newTag->parentClass != 0)
			insertTagItemMember(newTag);
		else
			insertTagItem(newTag);
	}
}

void ClassTree::appendTagItem(const Tag* tag)
{
	wxTreeItemId item = AppendItem(GetRootItem(), tag->titleFull, tagIcon(*tag), -1, new TagItemData(tag));
	itemByTags_[tag] = item;
	classNameToItem_[tag->nameFull].items.insert(item);
}

void ClassTree::rebuild()
{
	wxTreeItemId root = GetRootItem();
	DeleteChildren(root);
	itemByTags_.clear();
	classNameToItem_.clear();

	TagStorage::SortedTags::const_iterator it;
	for(int typeIndex = 0; typeIndex < TAG_TYPE_COUNT; ++typeIndex){
		TagType type = (TagType)typeIndex;
		const TagStorage::SortedTags& tags = tagStorage.sortedTags(TagType(typeIndex));

		FOR_EACH(tags, it){
			const Tag* tag = *it;
			if(tag->parentClass)
				insertTagItemMember(tag);

			if(matchFilter(*tag))
				appendTagItem(tag);
		}
	}
	
	if(!filterMember_.empty()){
		wxTreeItemIdValue cookie;
		wxTreeItemId current = GetFirstChild(root, cookie);
		while(current.IsOk()){
			wxTreeItemId previous = current;
			current = GetNextChild(root, cookie);
			if(GetChildrenCount(previous) == 0){
				const Tag* tag = tagByItem(previous);
				itemByTags_.erase(tag);
				if(tag->type == TAG_CLASS || tag->type == TAG_ENUM)
					classNameToItem_[tag->nameFull].items.erase(previous);
				Delete(previous);
			}
			else{
				if(!IsExpanded(previous))
					Expand(previous);
			}
		}
	}

	wxTreeItemId item = GetSelection();
	if(!item.IsOk()){
		wxTreeItemIdValue cookie;
		wxTreeItemId firstItem = GetFirstChild(root, cookie);
		if(firstItem.IsOk())
			ToggleItemSelection(firstItem);
	}
}

bool nameMatchFilter(const char* tagName, const std::string& filter_)
{
	int len = strlen(tagName) + 1;
	char* name = (char*)alloca(len);
	memcpy(name, tagName, len);
	strlwr(name);
	if(filter_.empty())
		return true;
	else{
		if(filter_.c_str()[0] == ' ')
			return strstr(name, filter_.c_str() + 1) != 0;
		else
			return strstr(name, filter_.c_str()) == name;
	}
}

bool ClassTree::matchFilterMember(const Tag& tag)
{
	if(nameMatchFilter(tag.nameFull, filterMember_))
		return true;
	return nameMatchFilter(tag.name, filterMember_);
}

bool ClassTree::matchFilter(const Tag& tag)
{
	if(nameMatchFilter(tag.nameFull, filter_))
		return true;
	return nameMatchFilter(tag.name, filter_);
}

const Tag* ClassTree::tagByItem(wxTreeItemId& item)
{
	TagItemData* data = static_cast<TagItemData*>(GetItemData(item));
	ASSERT(data);
	return data->tagPtr;
}


wxTreeItemId ClassTree::itemByTag(const Tag* tag, bool eraseFounded)
{
	ItemByTags::iterator it = itemByTags_.find(tag);
	if(it != itemByTags_.end()){
		wxTreeItemId result = it->second;
		if(eraseFounded)
			itemByTags_.erase(it);
		return result;
	}
	else
		return wxTreeItemId();
}

bool ClassTree::refilter(wxTreeItemId& item)
{
	bool isRoot = item == GetRootItem();
	if(!isRoot){
		if(filterMember_.empty() || filterMember_ == " ")
			return true;
		if(!IsExpanded(item))
			Expand(item);
	}

	bool hasRequestedMembers = false;
	wxTreeItemIdValue cookie;
	wxTreeItemId current = GetFirstChild(item, cookie);
	while(current.IsOk()){
		const Tag* tag = tagByItem(current);
#ifndef NDEBUG
		ASSERT(tag);
		wxTreeItemId ii = itemByTag(tag);
		if(ii != current){
			const Tag* missingTag = tagByItem(ii);
			ASSERT(0);
		}
#endif
		wxTreeItemId previous = current;
		current = GetNextChild(item, cookie);
		bool match = isRoot ? matchFilter(*tag) : matchFilterMember(*tag);
		if(!match || ((tag->type == TAG_CLASS || tag->type == TAG_ENUM) && !refilter(previous))){
			itemByTags_.erase(tagByItem(previous));
			Delete(previous);
		}
		else
			hasRequestedMembers = true;
	}
	return hasRequestedMembers;
}


void ClassTree::setFilter(const char* filter, const char* memberFilter)
{
	wxWindowUpdateLocker updateLocker(this);

	bool needRebuild = false;

	std::vector<char> newFilter(filter, filter + strlen(filter) + 1);
	if(newFilter.size() > 1)
		strlwr(&newFilter.front());
	if(filter_.empty())
		needRebuild = true;
	else if(strstr(&newFilter.front(), filter_.c_str()) != &newFilter.front())
		needRebuild = true;


	filter_ = &newFilter.front();

	std::vector<char> newMemberFilter(memberFilter, memberFilter + strlen(memberFilter) + 1);
	if(newMemberFilter.size() > 1)
		strlwr(&newMemberFilter.front());

	if(!needRebuild && strcmp(&newMemberFilter.front(), filterMember_.c_str()) != 0){
		if(filterMember_.empty())
			needRebuild = true;
		if(strstr(&newMemberFilter.front(), filterMember_.c_str()) != &newMemberFilter.front())
			needRebuild = true;
	}
	filterMember_ = &newMemberFilter.front();

	if(needRebuild)
		rebuild();
	else
		refilter(GetRootItem());
}

void ClassTree::onSelectionChanged(wxTreeEvent& event)
{
	const Tag* tag = tagByItem(event.GetItem());
	selectedTag_ = tag;
}

void ClassTree::onKeyDown(wxKeyEvent& event)
{
	if(event.GetKeyCode() != WXK_UP &&
		event.GetKeyCode() != WXK_HOME &&
		event.GetKeyCode() != WXK_PAGEUP &&
		event.GetKeyCode() != WXK_TAB){
		event.Skip();
		return;
	}


	wxTreeItemId item = GetSelection();

	wxTreeItemIdValue cookie;
	wxTreeItemId firstItem = GetFirstChild(GetRootItem(), cookie);

	if(event.GetKeyCode() == WXK_TAB ||
		event.GetModifiers() & wxMOD_CONTROL || 
		!firstItem.IsOk() || (item.IsOk() && firstItem == item)){
		wxKeyEvent newEvent = event;
		wxWindow* parent = GetParent();
		newEvent.SetEventObject(parent);
		newEvent.SetId(parent->GetId());
		parent->GetEventHandler()->ProcessEvent(newEvent);
	}
	else
		event.Skip();
}
